//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<DirectInputAdapter.cpp>
///	@path	~/src/appear/xwin/
///	@date	2007/11/11
///	@desc	Use Direct Input as adapter.

#include "config/config.h"

#include "appear/xwin/DirectInputAdapter.h"

#define	DINPUT_SAMPLE_BUFFER_SIZE		(16)
#define	DINPUT_DEFAULT_ACCURACY			(15)

#pragma comment( linker , "/defaultlib:dinput8.lib" )

namespace xeres {

	// ctor
	DirectInputAdapter::DirectInputAdapter( InputHandler * inputHandler )
		: m_inputState( 0 )
		, m_inputHandler( inputHandler )
		, m_dinput8( NULL )
		, m_keyboard8( NULL )
		, m_mouse8( NULL )
		, m_paused( false )
	{
		m_lastPos.x = m_lastPos.y = 0;
		m_accuracy.FromMilliSecond( DINPUT_DEFAULT_ACCURACY );
	}

	// dtor
	DirectInputAdapter::~DirectInputAdapter( void )
	{
		if( m_dinput8 )
		{
			// release
		}
	}

	// initialize device
	void DirectInputAdapter::InitializeDevice( HWND hWnd )
	{
		DWORD coopFlags = DISCL_EXCLUSIVE | DISCL_FOREGROUND | DISCL_NOWINKEY;

		HRESULT hr = S_OK;

		// Set the buffer size to DINPUT_BUFFERSIZE (defined above) elements.
		// The buffer size is a DWORD property associated with the device.
		DIPROPDWORD dipdw;
		dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
		dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
		dipdw.diph.dwObj        = 0;
		dipdw.diph.dwHow        = DIPH_DEVICE;
		dipdw.dwData            = DINPUT_SAMPLE_BUFFER_SIZE; // Arbitrary buffer size

		if( FAILED( hr = ::DirectInput8Create( ::GetModuleHandle(NULL) ,
			DIRECTINPUT_VERSION , IID_IDirectInput8 , (void**)&m_dinput8 , NULL ) ) )
		{
			// FIXME! Trace error
			return;
		}

		// create keyboard
		if( FAILED( hr = m_dinput8->CreateDevice( GUID_SysKeyboard , &m_keyboard8 , NULL ) ) )
		{
			// FIXME! Trace error
			return;
		}

		// Set the data format to "keyboard format" - a predefined data format 
		//
		// A data format specifies which controls on a device we
		// are interested in, and how they should be reported.
		//
		// This tells DirectInput that we will be passing an array
		// of 256 bytes to IDirectInputDevice::GetDeviceState.
		if( FAILED( hr = m_keyboard8->SetDataFormat( &c_dfDIKeyboard ) ) )
		{
			// FIXME! trace error
			return;
		}

		// Set the cooperativity level to let DirectInput know how
		// this device should interact with the system and with other
		// DirectInput applications.
		if( FAILED( hr = m_keyboard8->SetCooperativeLevel( hWnd , coopFlags ) ) )
		{
			// FIXME! trace error
			return;
		}

		// Set buffer size
		if( FAILED( hr = m_keyboard8->SetProperty( DIPROP_BUFFERSIZE , &dipdw.diph ) ) )
		{
			// FIXME! Trace error
			return;
		}

		// create mouse
		if( FAILED( hr = m_dinput8->CreateDevice( GUID_SysMouse , &m_mouse8 , NULL ) ) )
		{
			// FIXME! Trace error
			return;
		}

		// Set the data format to "mouse format" - a predefined data format 
		//
		// A data format specifies which controls on a device we
		// are interested in, and how they should be reported.
		//
		// This tells DirectInput that we will be passing a
		// DIMOUSESTATE2 structure to IDirectInputDevice::GetDeviceState.
		if( FAILED( hr = m_mouse8->SetDataFormat( &c_dfDIMouse2 ) ) )
		{
			// FIXME! Trace error
			return;
		}

		// Set the cooperativity level to let DirectInput know how
		// this device should interact with the system and with other
		// DirectInput applications.
		if( FAILED( hr = m_mouse8->SetCooperativeLevel( hWnd , coopFlags ) ) )
		{
			// FIXME! trace error
			return;
		}

		// Set buffer size
		if( FAILED( hr = m_mouse8->SetProperty( DIPROP_BUFFERSIZE , &dipdw.diph ) ) )
		{
			// FIXME! Trace error
			return;
		}

		Acquire();
	}

	// acquire
	void DirectInputAdapter::Acquire( void )
	{
		HRESULT hr = S_OK;

		hr = m_keyboard8->Acquire();
		hr = m_mouse8->Acquire();
		m_paused = false;
		m_lastUpdate = Time::Now();
	}

	// un-acquire
	void DirectInputAdapter::Unacquire( void )
	{
		m_keyboard8->Unacquire();
		m_mouse8->Unacquire();
		m_paused = true;
		m_lastUpdate = Time::Now();
	}

	// filter
	bool DirectInputAdapter::Filter( HWND hWnd , UINT message , WPARAM wParam , LPARAM lParam )
	{
		if( m_dinput8 == NULL )
		{
			InitializeDevice( hWnd );
		}

		switch( message )
		{
		case WM_ACTIVATE:
			// update state & position
			if( WA_INACTIVE != wParam )
			{
				// get key states
				BYTE kbuf[256];
				::GetKeyboardState( kbuf );

#define	KEY_STATE( KEY )		if( kbuf[ KeyInput::KEY_##KEY## ] )	\
								{ m_inputState |= Input::INPUT_STATE_##KEY##; } \
								else { m_inputState &= ~Input::INPUT_STATE_##KEY##; }

				KEY_STATE( LSHIFT );
				KEY_STATE( RSHIFT );
				KEY_STATE( LCTRL );
				KEY_STATE( RCTRL );
				KEY_STATE( LALT );
				KEY_STATE( RALT );
				KEY_STATE( CAPSLOCK );
				KEY_STATE( NUMLOCK );
				KEY_STATE( SCRLOCK );

#undef KEY_STATE

				Acquire();
			}
			// turn to next filter
			return false;

		case WM_ENTERMENULOOP:
			// Release the device, so if we are in exclusive mode the 
            // cursor will reappear
			Unacquire();
            return false;

		case WM_EXITMENULOOP:

			// Make sure the device is acquired when coming out of a menu loop
			Acquire();
            return false;
		}

		return false;
	}

	void DirectInputAdapter::Tick( Engine& engine )
	{
		// acquire
		if( !m_paused )
		{
			Time now = Time::Now();
			Time diff = now - m_lastUpdate;

//			if( diff > m_accuracy )
			{
				// get buffered data
				ProcessKeyInput();
				ProcessMouseInput();
//				m_lastUpdate = now;
			}
		}
	}

	void DirectInputAdapter::ProcessKeyInput( void )
	{
		DIDEVICEOBJECTDATA didod[ DINPUT_SAMPLE_BUFFER_SIZE ];
		DWORD dwElements = DINPUT_SAMPLE_BUFFER_SIZE;
		DWORD i = 0;
		HRESULT hr = S_OK;

		hr = m_keyboard8->GetDeviceData( sizeof(DIDEVICEOBJECTDATA) ,
			didod, &dwElements, 0 );

		if( hr != DI_OK ) 
		{
			// We got an error or we got DI_BUFFEROVERFLOW.
			//
			// Either way, it means that continuous contact with the
			// device has been lost, either due to an external
			// interruption, or because the buffer overflowed
			// and some events were lost.
			//
			// Consequently, if a button was pressed at the time
			// the buffer overflowed or the connection was broken,
			// the corresponding "up" message might have been lost.
			//
			// But since our simple sample doesn't actually have
			// any state associated with button up or down events,
			// there is no state to reset.  (In a real game, ignoring
			// the buffer overflow would result in the game thinking
			// a key was held down when in fact it isn't; it's just
			// that the "up" event got lost because the buffer
			// overflowed.)
			//
			// If we want to be cleverer, we could do a
			// GetDeviceState() and compare the current state
			// against the state we think the device is in,
			// and process all the states that are currently
			// different from our private state.
			hr = m_keyboard8->Acquire();
			while( hr == DIERR_INPUTLOST )
				hr = m_keyboard8->Acquire();

			// hr may be DIERR_OTHERAPPHASPRIO or other errors.  This
			// may occur when the app is minimized or in the process of 
			// switching, so just try again later 
			return;
		}

		// Study each of the buffer elements and process them.
		//
		// Since we really don't do anything, our "processing"
		// consists merely of squirting the name into our
		// local buffer.
		for( i = 0; i < dwElements; i++ ) 
		{
			bool press = (didod[i].dwData & 0x80) != 0;
			uint tag = 0;
			switch( didod[i].dwOfs )
			{
			case KeyInput::KEY_LCTRL:
				tag = Input::INPUT_STATE_LCTRL;
				break;
			case KeyInput::KEY_RCTRL:
				tag = Input::INPUT_STATE_RCTRL;
				break;
			case KeyInput::KEY_LALT:
				tag = Input::INPUT_STATE_LALT;
				break;
			case KeyInput::KEY_RALT:
				tag = Input::INPUT_STATE_RALT;
				break;
			case KeyInput::KEY_LSHIFT:
				tag = Input::INPUT_STATE_LSHIFT;
				break;
			case KeyInput::KEY_RSHIFT:
				tag = Input::INPUT_STATE_RSHIFT;
				break;
			}
			if( tag )
			{
				if( press )
					m_inputState |= tag;
				else
					m_inputState &= ~tag;
			}
			m_inputHandler->OnKeyInput( KeyInput( KeyInput::Key( didod[i].dwOfs ) , press , m_inputState ) );
		}
	}

	// process mouse input
	void DirectInputAdapter::ProcessMouseInput( void )
	{
		DIDEVICEOBJECTDATA didod[ DINPUT_SAMPLE_BUFFER_SIZE ];
		DWORD dwElements = DINPUT_SAMPLE_BUFFER_SIZE;
		DWORD i = 0;
		HRESULT hr = S_OK;

		hr = m_mouse8->GetDeviceData( sizeof(DIDEVICEOBJECTDATA) ,
			didod, &dwElements, 0 );

		if( hr != DI_OK ) 
		{
			// We got an error or we got DI_BUFFEROVERFLOW.
			//
			// Either way, it means that continuous contact with the
			// device has been lost, either due to an external
			// interruption, or because the buffer overflowed
			// and some events were lost.
			//
			// Consequently, if a button was pressed at the time
			// the buffer overflowed or the connection was broken,
			// the corresponding "up" message might have been lost.
			//
			// But since our simple sample doesn't actually have
			// any state associated with button up or down events,
			// there is no state to reset.  (In a real game, ignoring
			// the buffer overflow would result in the game thinking
			// a key was held down when in fact it isn't; it's just
			// that the "up" event got lost because the buffer
			// overflowed.)
			//
			// If we want to be cleverer, we could do a
			// GetDeviceState() and compare the current state
			// against the state we think the device is in,
			// and process all the states that are currently
			// different from our private state.
			hr = m_mouse8->Acquire();
			while( hr == DIERR_INPUTLOST )
				hr = m_mouse8->Acquire();

			// hr may be DIERR_OTHERAPPHASPRIO or other errors.  This
			// may occur when the app is minimized or in the process of 
			// switching, so just try again later
			return;
		}

		// Study each of the buffer elements and process them.
		//
		// Since we really don't do anything, our "processing"
		// consists merely of squirting the name into our
		// local buffer.

		for( i = 0; i < dwElements; i++ ) 
		{
			Input::Action action;
			POINTS pts = m_lastPos;

			switch( didod[i].dwOfs )
			{
			case DIMOFS_BUTTON0:
				// left button
				if( didod[i].dwData & 0x80 )
					action = Input::ACTION_MOUSE_LB_UP;
				else
					action = Input::ACTION_MOUSE_LB_CLICK;
				break;

			case DIMOFS_BUTTON1:
				// right button
				if( didod[i].dwData & 0x80 )
					action = Input::ACTION_MOUSE_RB_UP;
				else
					action = Input::ACTION_MOUSE_RB_CLICK;
				break;

			case DIMOFS_BUTTON2:
				// middle button
				if( didod[i].dwData & 0x80 )
					action = Input::ACTION_MOUSE_MB_UP;
				else
					action = Input::ACTION_MOUSE_MB_CLICK;
				break;

			case DIMOFS_X:
				{
					action = Input::ACTION_MOUSE_MOVE;
					union{ int i; DWORD d; } s;
					s.d = didod[i].dwData;
					pts.x += s.i;
				}
				break;
			case DIMOFS_Y:
				{
					action = Input::ACTION_MOUSE_MOVE;
					union{ int i; DWORD d; } s;
					s.d = didod[i].dwData;
					pts.y += s.i;
				}
				break;
			case DIMOFS_Z:
				{
					union{ int i; DWORD d; } s;
					s.d = didod[i].dwData;
					if( s.i > 0 )
						action = Input::ACTION_MOUSE_WHEEL_UP;
					else
						action = Input::ACTION_MOUSE_WHEEL_DOWN;
				}
				break;

			case DIMOFS_BUTTON3:
			case DIMOFS_BUTTON4:
			case DIMOFS_BUTTON5:
			case DIMOFS_BUTTON6:
			case DIMOFS_BUTTON7:

			default:
				continue;
			}
			m_lastPos = pts;
			m_inputHandler->OnMouseInput( MouseInput( m_lastPos.x , m_lastPos.y , action , m_inputState ) );
		}
	}

} // namespace xeres
